repo: Add `auto_transaction` and `TransactionGuard`
authorColin Walters <walters@verbum.org>
Tue, 28 Sep 2021 19:37:26 +0000 (15:37 -0400)
committerColin Walters <walters@verbum.org>
Fri, 6 May 2022 16:53:57 +0000 (12:53 -0400)
This gives auto-cancelling semantics on `Drop`, plus a nicer
`.commit()` method on the transaction.

Matches the currently private `_OstreeRepoAutoTransaction` in the C
library.

rust-bindings/rust/src/repo.rs
rust-bindings/rust/tests/util/mod.rs

index 3b4961cff4f61da38654a5636fdceb5b1a8a8d1e..432b3ee485997bc1fcbb22507a958de345f5aeff 100644 (file)
@@ -1,6 +1,6 @@
 #[cfg(any(feature = "v2016_4", feature = "dox"))]
 use crate::RepoListRefsExtFlags;
-use crate::{Checksum, ObjectName, ObjectType, Repo};
+use crate::{Checksum, ObjectName, ObjectType, Repo, RepoTransactionStats};
 use ffi;
 use glib::ffi as glib_sys;
 use glib::{self, translate::*, Error, IsA};
@@ -34,12 +34,51 @@ unsafe fn from_glib_container_variant_set(ptr: *mut glib_sys::GHashTable) -> Has
     set
 }
 
+/// An open transaction in the repository.
+///
+/// This will automatically invoke [`ostree::Repo::abort_transaction`] when the value is dropped.
+pub struct TransactionGuard<'a> {
+    /// Reference to the repository for this transaction.
+    repo: Option<&'a Repo>,
+}
+
+impl<'a> TransactionGuard<'a> {
+    /// Commit this transaction.
+    pub fn commit<P: IsA<gio::Cancellable>>(
+        mut self,
+        cancellable: Option<&P>,
+    ) -> Result<RepoTransactionStats, glib::Error> {
+        // Safety: This is the only function which mutates this option
+        let repo = self.repo.take().unwrap();
+        repo.commit_transaction(cancellable)
+    }
+}
+
+impl<'a> Drop for TransactionGuard<'a> {
+    fn drop(&mut self) {
+        if let Some(repo) = self.repo {
+            // TODO: better logging in ostree?
+            // See also https://github.com/ostreedev/ostree/issues/2413
+            let _ = repo.abort_transaction(gio::NONE_CANCELLABLE);
+        }
+    }
+}
+
 impl Repo {
     /// Create a new `Repo` object for working with an OSTree repo at the given path.
     pub fn new_for_path<P: AsRef<Path>>(path: P) -> Repo {
         Repo::new(&gio::File::for_path(path.as_ref()))
     }
 
+    /// A wrapper for [`prepare_transaction`] which ensures the transaction will be aborted when the guard goes out of scope.
+    pub fn auto_transaction<P: IsA<gio::Cancellable>>(
+        &self,
+        cancellable: Option<&P>,
+    ) -> Result<TransactionGuard, glib::Error> {
+        let _ = self.prepare_transaction(cancellable)?;
+        Ok(TransactionGuard { repo: Some(self) })
+    }
+
     /// Return a copy of the directory file descriptor for this repository.
     #[cfg(any(feature = "v2016_4", feature = "dox"))]
     #[cfg_attr(feature = "dox", doc(cfg(feature = "v2016_4")))]
index d4a83478cf2a7512ef2280c10f0c936fea1a7312..a51c0521c6ae64807a11cb6d934b4494b5024e1c 100644 (file)
@@ -42,7 +42,8 @@ pub fn create_mtree(repo: &ostree::Repo) -> ostree::MutableTree {
 }
 
 pub fn commit(repo: &ostree::Repo, mtree: &ostree::MutableTree, ref_: &str) -> GString {
-    repo.prepare_transaction(NONE_CANCELLABLE)
+    let txn = repo
+        .auto_transaction(NONE_CANCELLABLE)
         .expect("prepare transaction");
     let repo_file = repo
         .write_mtree(mtree, NONE_CANCELLABLE)
@@ -60,8 +61,7 @@ pub fn commit(repo: &ostree::Repo, mtree: &ostree::MutableTree, ref_: &str) -> G
         )
         .expect("write commit");
     repo.transaction_set_ref(None, ref_, checksum.as_str().into());
-    repo.commit_transaction(NONE_CANCELLABLE)
-        .expect("commit transaction");
+    txn.commit(NONE_CANCELLABLE).expect("commit transaction");
     checksum
 }